﻿using FxSuperCore.Log;
using NLua;
using NLua.Exceptions;
using ScriptEngine.Script.Exceptions;
using ScriptEngine.Script.Extension;
using System;
using System.Reflection;
using System.Text;
using System.Threading;

namespace ScriptEngine.Script.Executor.Impl
{
	public class LuaExecutor : AbstractScriptExecutor
	{
		public ILogPrint Logger { get; set; } = new PluginLoggerPrinter("LuatExecutor", new ConsoleLogPrinter());

		private volatile Lua lua;

		private static readonly int MaxRequireTableCount = 100;

		private static volatile int CurTableIdx = -1;

		/// <summary>
		/// 从数据源获取自定义方法/属性注册到lua中
		/// </summary>
		/// <param name="luaEngine">lua环境</param>
		private void RegisterCsFunc(Lua luaEngine)
		{
			foreach (CodeTipPojo codeTipPojo in Datasource)
			{
				if (codeTipPojo.ScriptCallCsharp.Type == ExtensionType.Func)
				{
					// 注入方法
					luaEngine.RegisterFunction(codeTipPojo.RealName, codeTipPojo.ParentObj, codeTipPojo.Instance as MethodInfo);
				}
				else
				{
					// 注入属性或常量
					luaEngine[codeTipPojo.RealName] = codeTipPojo.Instance;
				}
			}
		}


		public override T ExeFunc<T>(string funcName, params object[] args)
		{
			AssertEngineIsRunning();
			Monitor.Enter(locker);
			try
			{
				object[] res = lua.GetFunction(funcName)?.Call(args);
				if (res != null && res.Length >= 1)
				{
					return (T)res[0];
				}
			}
			catch (Exception e)
			{
				Logger.Error($"lua script exec error:{e.Message}");
				throw e;
			}
			finally
			{
				Monitor.Exit(locker);
			}

			return default;
		}

		public override void ExecFunc(string funcName, params object[] args)
		{
			AssertEngineIsRunning();
			Monitor.Enter(locker);
			try
			{
				lua.GetFunction(funcName)?.Call(args);
			}
			catch (Exception e)
			{
				Logger.Error($"lua script exec error:{e.Message}");
				throw e;
			}
			finally
			{
				Monitor.Exit(locker);
			}
		}

		public override void RunWithCode(string code)
		{
			if (IsRunning)
			{
				throw new ScriptException("Script already running");
			}
			Monitor.Enter(locker);
			try
			{
				Interlocked.Exchange(ref CurTableIdx, -1);
				lua = new Lua();
				lua.LoadCLRPackage();
				lua.State.Encoding = Encoding.UTF8;
				EngineStatus = ScriptEngineStatus.PREPARE_RUNNING;
				RegisterCsFunc(lua);
				lua.DoString(code);
				EngineStatus = ScriptEngineStatus.RUNNING;
			}
			catch (ThreadAbortException)
			{
				Logger.Info("force exit with ThreadAbortException");
				Exit();
				return;
			}
			catch (LuaScriptException e)
			{
				Logger.Error($"lua script parse error: {Environment.NewLine}\t{e.Message}:{Environment.NewLine}\t{e.InnerException?.Message ?? ""}");
				Exit();
				return;
			}
			catch (Exception e)
			{
				Logger.Error($"lua script load exception: {e.Message}");
				Exit();
				return;
			}
			finally
			{
				Monitor.Exit(locker);
			}
			HandleEventLoop();
		}

		public override void RunWithPath(string filepath)
		{
			if (IsRunning)
			{
				throw new ScriptException("Script already running");
			}
			Monitor.Enter(locker);
			try
			{
				Interlocked.Exchange(ref CurTableIdx, -1);
				lua = new Lua();
				lua.LoadCLRPackage();
				lua.State.Encoding = Encoding.UTF8;
				EngineStatus = ScriptEngineStatus.PREPARE_RUNNING;
				RegisterCsFunc(lua);
				lua.DoFile(filepath);
				EngineStatus = ScriptEngineStatus.RUNNING;
			}
			catch (ThreadAbortException)
			{
				Logger.Info("force exit with ThreadAbortException");
				Exit();
				return;
			}
			catch (LuaScriptException e)
			{
				Logger.Error($"lua script parse error: {Environment.NewLine}\t{e.Message}:{Environment.NewLine}\t{e.InnerException?.Message ?? ""}");
				Exit();
				return;
			}
			catch (Exception e)
			{
				Logger.Error($"lua script load exception: {e.Message}");
				Exit();
				return;
			}
			finally
			{
				Monitor.Exit(locker);
			}
			HandleEventLoop();
		}

		public override object GetObject(string symbol)
		{
			AssertEngineIsRunning();
			return lua.GetTable(symbol);
		}

		public LuaTable RequireTable()
		{
			AssertEngineIsRunning();
			int idx;
			lock (locker)
			{
				idx = Interlocked.Increment(ref CurTableIdx);
				if (idx > MaxRequireTableCount)
				{
					idx %= MaxRequireTableCount;
					Interlocked.Exchange(ref CurTableIdx, idx);
				}
			}

			string name = $"#__tmp_require_table__#{idx}";
			lock (locker)
			{
				LuaTable luaTable = lua.GetTable(name);
				if (luaTable != null)
				{
					luaTable.Dispose();
				}
				lua.NewTable(name);
			}

			return lua.GetTable(name);
		}

		public override void Exit()
		{
			base.Exit();
			if (IsRunning)
			{
				lua?.Dispose();
				lua = null;
				EngineStatus = ScriptEngineStatus.EXITED;
				Logger.Info("lua script exec done");
			}
		}
	}
}
